package com.ybear.ybutils.utils;

import android.os.Message;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;

import com.ybear.ybutils.utils.handler.Handler;
import com.ybear.ybutils.utils.handler.HandlerManage;
import com.ybear.ybutils.utils.log.LogUtil;
import com.ybear.ybutils.utils.time.DateTime;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * <h1>心跳定时器</h1>
 * @see LiveTime
 *
 *
 * <h3>******** 更新心跳包 ********</h3>
 * @see LiveTime#updateLiveTimeIntervalSecond(int, long) 
 * 更新心跳间隔（秒）
 * @see LiveTime#updateLiveTimeIntervalMilliseconds(int, long) 
 * 更新心跳间隔（毫秒）
 *
 * @see LiveTime#updateLiveTimeSecond(int, long)
 * 更新心跳存活时间（秒）
 * @see LiveTime#updateLiveTimeMilliseconds(int, long) 
 * 更新心跳存活时间（毫秒）
 * 
 *
 * <h3>******** 启动心跳包 ********</h3>
 * @see LiveTime#startLiveTime(int...)
 * 启动心跳包（默认Handler）
 * @see LiveTime#startLiveTime(Handler, int...)
 * 启动心跳包（调用者Handler）
 *
 *
 * <h3>******** 检查心跳包 ********</h3>
 * @see LiveTime#checkLiveTime(int...)
 * 检查心跳包
 * @see LiveTime#checkLiveTime(Handler, int...)
 * 检查心跳包（调用者Handler）
 * @see LiveTime#checkLiveTime(boolean, int...)
 * 检查心跳包（允许强制退出线程）
 * @see LiveTime#checkLiveTime(Handler, boolean, int...)
 * 检查心跳包（调用者Handler，允许强制退出线程）
 *
 *
 * <h3>******** 停止心跳包 ********</h3>
 * @see LiveTime#stopLiveTime(int...)
 * 停止心跳包
 * @see LiveTime#stopLiveTime(Handler, int...)
 * 停止心跳包（调用者Handler）
 *
 *
 * <h3>******** 释放心跳包 ********</h3>
 * @see LiveTime#releaseLiveTime(int...)
 * 释放心跳包
 * @see LiveTime#releaseLiveTime(Handler, int...)
 * 释放心跳包（调用者Handler）
 *
 *
 * <h3>******** 心跳定时器接口 ********</h3>
 * @see LiveTime#addOnLiveTimeListener(OnLiveTimeListener)
 * 添加心跳监听器
 * @see LiveTime#removeOnLiveTimeListener(OnLiveTimeListener)
 * 移除心跳监听器
 *
 *
 * <h3>******** 心跳定时器 ********</h3>
 * @see OnLiveTimeListener#onCreate(int)
 * 创建心跳包
 * @see OnLiveTimeListener#onLiveTime(int)
 * 触发心跳包
 * @see OnLiveTimeListener#onStop(int)
 * 停止心跳包
 * @see OnLiveTimeListener#onDestroy(int)
 * 销毁心跳包
 */
public class LiveTime {
    private static final String TAG = "LiveTimeTAG";
    private final Map<Integer, LiveRunnable> mLiveRunnableMap = new ConcurrentHashMap<>();
    private final List<OnLiveTimeListener> mOnLiveTimeList = new CopyOnWriteArrayList<>();

    private LiveTime() {}
    public static LiveTime get() { return H.I; }
    private static final class H { private static final LiveTime I = new LiveTime(); }

    private Handler mCreateHandler;
    @NonNull
    private Handler createHandler(LiveRunnable run) {
        if( run != null ) mCreateHandler = run.getHandler();
        if( mCreateHandler == null ) mCreateHandler = HandlerManage.create();
        return mCreateHandler;
    }
    private LiveRunnable getLiveRunnable(int id, boolean isCreate) {
        LiveRunnable run = mLiveRunnableMap.containsKey( id ) ? mLiveRunnableMap.get( id ) : null;
        if( isCreate ) {
            if( run == null ) run = newLiveRunnable();
            run.setId( id );
            //心跳创建时间
            run.createTime();
            //最后一次触发的时间
            run.createLastLiveTime();
            mLiveRunnableMap.put( id, run );
        }
        return run;
    }
    private void sendMessage(int id, @NonNull LiveRunnable run, long delayMillis) {
        try {
            Handler handler = run.getHandler();
            if( handler == null ) run.setHandler( handler = createHandler( run ) );
            handler.removeCallbacks( run, id );
            Message msg = Message.obtain( handler.getOsHandler(), run );
            msg.obj = id;
            if( delayMillis > 0 ) {
                handler.sendMessageDelayed( msg, delayMillis );
            }else {
                handler.sendMessage( msg );
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * LiveRunnable是否存活
     * @param run       run
     * @return          是否存活
     */
    private boolean isLive(LiveRunnable run) {
        if( run == null ) return false;
        //当前时间
        long currentTimeMillis = DateTime.currentTimeMillis();
        //最后一次心跳的时间
        long lastLiveTime = run.getLastLiveTime();
        //心跳包创建时间
        long createTime = run.getCreateTime();
        //心跳间隔
        long liveTimeInterval = run.getLiveTimeMillisecondsInterval();
        //心跳存活时间
        long liveTime = run.getLiveTimeMilliseconds();
        long maxWaitOver = run.getMaxWaitOverLiveTimeMilliseconds();
        LogUtil.d( TAG, "LiveTime -> isLive -> " +
                        "currentTimeMillis:%s, maxWaitOver:%s, diff:%s, lastLiveTime:%s, createTime:%s, " +
                        "liveTimeInterval:%s, liveTime:%s, " +
                        "liveTime+timeInterval:%s, curTime-createTime:%s",
                currentTimeMillis, maxWaitOver, ( lastLiveTime + liveTimeInterval + maxWaitOver ),
                lastLiveTime, createTime, liveTimeInterval, liveTime,
                ( lastLiveTime + liveTimeInterval >= currentTimeMillis ),
                ( currentTimeMillis - createTime <= liveTime )
        );
        //满足心跳间隔 和 存活时间两个条件
        return lastLiveTime + liveTimeInterval + maxWaitOver >= currentTimeMillis &&
                ( liveTime <= 0 || currentTimeMillis - createTime <= liveTime );
    }

    /**
     * 更新指定id的最长存活时间
     * 注：如果id不存在则自动创建
     * @param id                id
     * @param milliseconds      存活时间（毫秒）
     */
    public void updateLiveTimeMilliseconds(int id, long milliseconds) {
        LiveRunnable run = getLiveRunnable( id, true );
        run.setLiveTimeMilliseconds( milliseconds );
        mLiveRunnableMap.put( id, run );
        LogUtil.d( TAG, "LiveTime -> liveTimeInterval -> id:%s, milliseconds:%s",
                id, milliseconds
        );
    }

    /**
     * 更新指定id的最长存活时间
     * 注：如果id不存在则自动创建
     * @param id                id
     * @param seconds           存活时间（秒）
     */
    public void updateLiveTimeSecond(int id, long seconds) {
        updateLiveTimeMilliseconds( id, seconds * 1000 );
    }

    /**
     * 更新指定id的心跳时间
     * 注：如果id不存在则自动创建
     * @param id                id
     * @param milliseconds      存活时间（毫秒）
     */
    public void updateLiveTimeIntervalMilliseconds(int id, long milliseconds) {
        LiveRunnable run = getLiveRunnable( id, true );
        run.setLiveTimeIntervalMilliseconds( milliseconds );
        mLiveRunnableMap.put( id, run );
        LogUtil.d( TAG, "LiveTime -> liveTimeInterval -> id:%s, milliseconds:%s",
                id, milliseconds
        );
    }

    /**
     * 更新指定id的心跳时间
     * 注：如果id不存在则自动创建
     * @param id                id
     * @param seconds           存活时间（秒）
     */
    public void updateLiveTimeIntervalSecond(int id, long seconds) {
        updateLiveTimeIntervalMilliseconds( id, seconds * 1000 );
    }

    /**
     * 开始心跳
     * 注：如果id不存在则自动创建
     * @param handler           Handler, 传空将自动创建
     * @param ids               开始的ids
     */
    public void startLiveTime(@Nullable Handler handler, int... ids) {
        //如果不传任何id，则启动现有全部id
        if( ids == null || ids.length == 0 ) {
            for( Integer id : mLiveRunnableMap.keySet() ) {
                startLiveTime( id, handler );
            }
            return;
        }
        if( ids.length == 1 ) {
            startLiveTime( ids[ 0 ], handler );
            return;
        }
        for( int id : ids ) startLiveTime( id, handler );
    }
    /**
     * 开始心跳
     * 注：如果id不存在则自动创建
     * @param ids               开始的ids
     */
    public void startLiveTime(int... ids) { startLiveTime( null, ids ); }

    private void startLiveTime(int id, @Nullable Handler handler) {
        LiveRunnable run = getLiveRunnable( id, true );
        if( handler == null ) handler = createHandler( run );
        run.setHandler( handler );
        run.setRunnable( true );
        //发送消息
        sendMessage( id, run, 0 );
        //创建心跳包
        doLiveTimeListener( 0, run );
        LogUtil.d( TAG, "LiveTime -> startLiveTime -> id:%s, lastLiveTime:%s", id, run.getLastLiveTime() );
    }

    /**
     * 停止心跳
     * 注：如果id不存在则自动创建
     * @param handler           Handler, 传空将自动创建
     * @param ids               停止的ids
     */
    public void stopLiveTime(@Nullable Handler handler, int... ids) {
        //如果不传任何id，则停止现有全部id
        if( ids == null || ids.length == 0 ) {
            for( Integer id : mLiveRunnableMap.keySet() ) {
                stopLiveTime( id, handler );
            }
            return;
        }
        if( ids.length == 1 ) {
            stopLiveTime( ids[ 0 ], handler );
            return;
        }
        for( int id : ids ) stopLiveTime( id, handler );
    }
    /**
     * 停止心跳
     * 注：如果id不存在则自动创建
     * @param ids               停止的ids
     */
    public void stopLiveTime(int... ids) { stopLiveTime( null, ids ); }

    @Nullable
    private LiveRunnable stopLiveTime(int id, @Nullable Handler handler) {
        try {
            LiveRunnable run = getLiveRunnable( id, false );
            if( handler == null ) handler = createHandler( run );
            if( run != null ) {
                run.setHandler( handler );
                run.setRunnable( false );
                run.clearLastLiveTime();
                mLiveRunnableMap.put( id, run );
                handler.removeCallbacks( run, id );
                //停止心跳包
                doLiveTimeListener( 2, run );
            }else {
                handler.removeCallbacksAndMessages( id );
            }
            LogUtil.d( TAG, "LiveTime -> stopLiveTime -> id:" + id );
            return run;
        }catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 指定的id是否在运行
     * @param existIdCall   回调存在的id（如果为null则减少new ArrayList的开销）
     * @param ids           检查的ids
     * @return              有一个id不存在时（existIdCall 不为空时完全遍历），返回false
     */
    public boolean isRunnable(@Nullable Consumer<List<Integer>> existIdCall, int... ids) {
        if( ids == null || ids.length == 0 ) return false;
        List<Integer> existList = existIdCall == null ? null : new ArrayList<>();
        for (int id : ids) {
            if( mLiveRunnableMap.containsKey( id ) ) {
                LiveRunnable liveRun = mLiveRunnableMap.get( id );
                if( liveRun == null || existList == null ) continue;
                if( liveRun.isRunnable ) existList.add( id );
                continue;
            }
            if( existList == null ) return false;
        }
        if( existIdCall != null ) existIdCall.accept( existList );
        return true;
    }

    /**
     * 指定的id是否在运行
     * @param ids           检查的ids
     * @return              有一个id不存在时，返回false
     */
    public boolean isRunnable(int... ids) { return isRunnable( null, ids ); }

    /**
     * 检查心跳, 防止心跳突然停止（可以在onResume or onPause, 或者其他经常触发的地方增加）
     * 注：如果id不存在则跳过
     * @param handler           Handler, 传空将自动创建
     * @param isForcedExit      是否强制结束心跳
     * @param ids               检查的ids, 不传或null时，检查所有id
     */
    public void checkLiveTime(@Nullable Handler handler, boolean isForcedExit, int... ids) {
        //如果不传任何id，则检查现有全部id
        if( ids == null || ids.length == 0 ) {
            for( Integer id : mLiveRunnableMap.keySet() ) {
                checkLiveTime( id, handler, isForcedExit );
            }
            return;
        }
        if( ids.length == 1 ) {
            checkLiveTime( ids[ 0 ], handler, isForcedExit );
            return;
        }
        for( int id : ids ) checkLiveTime( id, handler, isForcedExit );
    }
    /**
     * 检查心跳, 防止心跳突然停止（可以在onResume or onPause, 或者其他经常触发的地方增加）
     * 注：如果id不存在则跳过
     * @param isForcedExit      是否强制结束心跳
     * @param ids               检查的ids, 不传或null时，检查所有id
     */
    public void checkLiveTime(boolean isForcedExit, int... ids) {
        checkLiveTime( null, isForcedExit, ids );
    }
    /**
     * 检查心跳, 防止心跳突然停止（可以在onResume or onPause, 或者其他经常触发的地方增加）
     * 注：如果id不存在则跳过
     * @param handler           Handler, 传空将自动创建
     * @param ids               检查的ids, 不传或null时，检查所有id
     */
    public void checkLiveTime(@Nullable Handler handler, int... ids) {
        checkLiveTime( handler, false, ids );
    }
    /**
     * 检查心跳, 防止心跳突然停止（可以在onResume or onPause, 或者其他经常触发的地方增加）
     * 注：如果id不存在则跳过
     * @param ids               检查的ids, 不传或null时，检查所有id
     */
    public void checkLiveTime(int... ids) { checkLiveTime( null, ids ); }
    private void checkLiveTime(int id, @Nullable Handler handler, boolean isForcedExit) {
        LiveRunnable run = getLiveRunnable( id, false );
        if( run == null ) return;
        long lastLiveTime = run.getLastLiveTime();
        long liveTimeInterval = run.getLiveTimeMillisecondsInterval();
        //强制退出
        if( isForcedExit ) {
            if( lastLiveTime > 0 ) {
                stopLiveTime( id, handler );
                LogUtil.d( TAG, "LiveTime -> stopLiveTime id:%s, Forced exit.", id );
            }
            return;
        }
        if( isLive( run ) ) return;
        LogUtil.d(TAG, "LiveTime -> checkLiveTime -> reStart -> " +
                        "id:%s, lastLiveTime:%s, liveTimeInterval:%s",
                id, lastLiveTime, liveTimeInterval
        );
        startLiveTime( id, handler );
    }

    /**
     * 释放心跳，释放后 {@link LiveTime#checkLiveTime(int...)} 将失效，
     * 需要重新调用 {@link LiveTime#startLiveTime(int...)} 才能检查心跳
     * 注：如果id不存在则自动创建
     * @param handler           Handler, 传空将自动创建
     * @param ids               检查的ids, 不传或null时，释放所有id
     */
    public void releaseLiveTime(@Nullable Handler handler, int... ids) {
        //如果不传任何id，则释放现有全部id
        if( ids == null || ids.length == 0 ) {
            for( Integer id : mLiveRunnableMap.keySet() ) {
                releaseLiveTime( id, handler );
            }
            return;
        }
        if( ids.length == 1 ) {
            releaseLiveTime( ids[ 0 ], handler );
            return;
        }
        for( int id : ids ) releaseLiveTime( id, handler );
    }
    /**
     * 释放心跳，释放后 {@link LiveTime#checkLiveTime(int...)} 将失效，
     * 需要重新调用 {@link LiveTime#startLiveTime(int...)} 才能检查心跳
     * 注：如果id不存在则自动创建
     * @param ids               检查的ids, 不传或null时，释放所有id
     */
    public void releaseLiveTime(int... ids) { releaseLiveTime( null, ids ); }

    private void releaseLiveTime(int id, @Nullable Handler handler) {
        LiveRunnable run = stopLiveTime( id, handler );
        mLiveRunnableMap.remove( id );
        //释放心跳包
        if( run != null ) doLiveTimeListener( 3, run );
        LogUtil.d( TAG, "LiveTime -> releaseLiveTime -> id:" + id );
    }

    /**
     * 监听心跳回调
     * @param listener          监听器
     */
    public void addOnLiveTimeListener(OnLiveTimeListener listener) {
        try {
            if( !mOnLiveTimeList.contains( listener ) ) mOnLiveTimeList.add( listener );
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 移除心跳回调
     * @param listener          监听器
     */
    public void removeOnLiveTimeListener(OnLiveTimeListener listener) {
        try {
            mOnLiveTimeList.remove( listener );
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    public interface OnLiveTimeListener {
        /**
         * 心跳包创建
         * @param id    心跳id
         */
        void onCreate(int id);
        /**
         心跳
         @param id      心跳id
         @return        拦截这个心跳（下次不会触发）
         */
        boolean onLiveTime(int id);

        /**
         * 心跳包停止
         * @param id    心跳id
         */
        void onStop(int id);

        /**
         * 心跳包销毁
         * @param id    心跳id
         */
        void onDestroy(int id);
    }

    private static class LiveRunnable implements Runnable {
        private int id;
        private Handler handler;
        private long lastLiveTime;
        private long liveTimeInterval = 10 * 1000;   //默认10秒发起一次心跳
        private long liveTime = 0;                      //存活时间，默认一直存活
        private long createTime;                        //创建时间
        //最长等待时间：默认心跳时长的0.1倍数（超时后多久之内允许继续心跳）
        private long maxWaitOverLiveTime = -1;
        private boolean isFromUserByMaxWaitOver = false;
        private boolean isRunnable;

        @Override
        public void run() { }

        public int getId() { return id; }
        public void setId(int id) { this.id = id; }
        public Handler getHandler() { return handler; }
        public void setHandler(Handler handler) { this.handler = handler; }
        public long getLiveTimeMillisecondsInterval() { return liveTimeInterval; }
        public void setLiveTimeIntervalSecond(long second) {
            setLiveTimeIntervalMilliseconds( second * 1000 );
        }
        public void setLiveTimeIntervalMilliseconds(long milliseconds) {
            liveTimeInterval = milliseconds;
            //刷新最长等待时间
            if( !isFromUserByMaxWaitOver ) {
                maxWaitOverLiveTime = -1;
                createMaxWaitOverLiveTime();
            }
        }
        public long getCreateTime() { return createTime; }
        void createTime() { createTime = DateTime.currentTimeMillis(); }

        public long getMaxWaitOverLiveTimeMilliseconds() { return maxWaitOverLiveTime; }
        public void setMaxWaitOverLiveTimeMilliseconds(long ts) {
            isFromUserByMaxWaitOver = true;
            this.maxWaitOverLiveTime = ts;
        }
        void createMaxWaitOverLiveTime() {
            if( isFromUserByMaxWaitOver || maxWaitOverLiveTime >= 0 ) return;
            //最长等待时间：默认心跳时长的0.1倍数（超时后多久之内允许继续心跳）
            maxWaitOverLiveTime = ObjUtils.parseLong(
                    getLiveTimeMillisecondsInterval() * 0.1D
            );
        }

        public long getLiveTimeMilliseconds() { return liveTime; }
        void setLiveTimeMilliseconds(long milliseconds) { liveTime = milliseconds; }
        public boolean isRunnable() { return isRunnable; }
        public void setRunnable(boolean runnable) { isRunnable = runnable; }

        long getLastLiveTime() { return lastLiveTime; }
        void createLastLiveTime() { this.lastLiveTime = DateTime.currentTimeMillis(); }
        void clearLastLiveTime() { this.lastLiveTime = 0; }
    }

    private LiveRunnable newLiveRunnable() {
        return new LiveRunnable() {//记录在麦位心跳
            @Override
            public void run() {
                //触发心跳包
                doLiveTimeListener( 1, this );
            }
        };
    }

    private void doLiveTimeListener(int type, @NonNull LiveRunnable run) {
        for( OnLiveTimeListener listener : mOnLiveTimeList ) {
            if( listener == null ) continue;
            int id = run.getId();
            boolean isLive = isLive( run );
            boolean isRunnable = run.isRunnable();
            switch( type ) {
                case 0:         //创建心跳包
                    listener.onCreate( id );
                    break;
                case 1:         //触发心跳包
                    Handler handler = run.getHandler();
                    boolean isReturn = listener.onLiveTime( id );
                    if( isReturn ) {
                        LogUtil.d( TAG, "LiveTime -> LiveRunnable -> id:%s, exit run", id );
                        continue;
                    }
                    //下一次心跳
                    if( isLive ) {
                        run.createLastLiveTime();
                        if( isRunnable ) sendMessage( id, run, run.getLiveTimeMillisecondsInterval() );
                    }else {
                        stopLiveTime( id, handler );
                    }
                    break;
                case 2:         //停止心跳包
                    listener.onStop( id );
                    break;
                case 3:         //销毁心跳包
                    listener.onDestroy( id );
                    break;
            }
            LogUtil.d(
                    TAG, "LiveTime -> LiveRunnable -> id:%s, isRun:%s, isLive:%s, runnable...",
                    id, isRunnable, isLive
            );
        }
    }
}
